package _线性;

import java.util.*;

class _排序 {

    // 冒泡排序
    public static void bubbleSort(int[] arr) {
        int n = arr.length;
        boolean swapped;  // 标志位，用于检测本轮遍历是否有交换
        for (int i = 0; i < n - 1; i++) {
            swapped = false;  // 每轮开始前，置标志位为 false
            //从前向后冒泡
            for (int j = 0; j < n - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    swap(arr, j, j + 1);
                    swapped = true;
                }
            }
            // 如果没有交换，说明数组已经有序，提前结束
            if (!swapped) {
                break;
            }
        }
    }

    // 选择排序方法：不断选择未排序部分中的最小值，并将其放到已排序部分的末尾
    public static void selectionSort(int[] arr) {
        int n = arr.length;
        for (int i = 0; i < n - 1; i++) {
            // 假设第 i 个元素是最小值
            int min = i;
            // 在剩下的未排序部分中寻找最小值
            for (int j = i + 1; j < n; j++) {
                if (arr[j] < arr[min]) {
                    min = j; // 更新最小值的索引
                }
            }
            // 如果找到的最小值不是当前元素，则进行交换
            if (min != i) {
                swap(arr, i, min);
            }
        }
    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    private static void swap(String[] arr, int i, int j) {
        String temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    // 插入排序：
    //  从第二个元素开始（假设第一个元素已排序），将当前元素与已排序部分的元素进行比较。
    //  找到合适的位置，将当前元素插入到已排序部分中。（从后向前）
    //  重复步骤 1 和 2，直到整个数组排序完成。
    public static void insertionSort(int[] arr) {
        int n = arr.length;
        for (int i = 1; i < n; i++) {
            int key = arr[i];
            int j = i - 1;
            // 找到合适的位置，将当前元素插入到已排序部分中。
            while (j >= 0 && arr[j] > key) {
                arr[j + 1] = arr[j];
                j--;
            }
            arr[j + 1] = key;
        }
    }

    /**
     * 快速排序：  单层递归逻辑  ，前序
     * 时间复杂度：
     * 最坏情况：O(n²)，例如当输入数组已经有序或逆序，且每次都选择了最大或最小元素作为基准。
     * 平均情况：O(n log n)，这是快速排序的期望时间复杂度。
     * 最好情况：O(n log n)，当每次都能将数组均匀地分为两部分时。
     * 空间复杂度：
     * O(log n)
     * 通过一个“基准”（pivot）元素将数组分成两部分，
     * 使得左侧部分的所有元素都小于基准，右侧部分的所有元素都大于基准，然后递归地对这两部分进行排序。
     * [7,9,3,6,2,5]
     */
    public static void quickSort(int[] arr, int low, int high) {
        //递归进行/终止条件
        if (low < high) {
            // 获取分区后基准元素的索引
            int pi = partition(arr, low, high);
            quickSort(arr, low, pi - 1);  // 排序基准左侧的子数组
            quickSort(arr, pi + 1, high); // 排序基准右侧的子数组
        }
    }

    // 7 2 1 6 5 （i = -1）
    // 2 7 1 6 5 （i = 0）
    // 2 1 7 6 5 （i = 1）
    // 2 1 5 6 7  return 2
    private static int partition(int[] arr, int l, int r) {
        int p = arr[r];
        int i = l - 1; // 小于基准元素 的位置
        for (int j = l; j < r; j++) {
            // 每遇到小于基准元素的值，就交换到前面去
            if (arr[j] < p) {
                swap(arr, ++i, j);
            }
        }
        // 把基准元素放到正确的位置
        swap(arr, i + 1, r);
        return i + 1;
    }

    // 归并排序，直接当成递归来写
    // 将一个大的数组分成两个较小的数组，
    // 递归地对这两个小数组进行排序，然后将它们合并成一个有序的数组。

    /**
     * 时间复杂度：
     * 最坏情况：O(n log n)
     * 平均情况：O(n log n)
     * 最好情况：O(n log n)
     * <p>
     * 空间复杂度：O(n)
     */
    public static void mergeSort(int[] arr) {
        //递归终止条件
        if (arr.length < 2) return;
        //单层递归逻辑
        int mid = arr.length / 2;
        int[] l = new int[mid];
        int[] r = new int[arr.length - mid];

        System.arraycopy(arr, 0, l, 0, mid);
        System.arraycopy(arr, mid, r, 0, arr.length - mid);
        mergeSort(l);
        mergeSort(r);

        // 合并已排序的数组
        merge(arr, l, r);
    }

    private static void merge(int[] arr, int[] l, int[] r) {
        int i = 0, j = 0, k = 0;
        while (i < l.length && j < r.length) {
            if (l[i] <= r[j]) {
                arr[k++] = l[i++];
            } else
                arr[k++] = r[j++];
        }
        while (i < l.length) {
            arr[k++] = l[i++];
        }
        while (j < r.length) {
            arr[k++] = r[j++];
        }
    }

    // 计数排序算法：：适用于有限范围内的数值进行排序，【在草稿纸上模拟一边】
    // 通过统计每个元素出现的次数，然后将其放到正确的位置，，计数、累加 =>
    /*          从后向前遍历是为了保持相同元素的稳定性
                count-- 是为了保证从后向前第二个相同元素的索引正确
        时间复杂度：O(n + k)，
            其中 n 是待排序元素的数量，k 是数值范围（最大值与最小值之差）。因此，如果 k 远大于 n，则计数排序的效率会下降。
        空间复杂度：O(k)，
    *
    * */
    public static void countingSort(int[] arr) {
        if (arr.length == 0) return;
        int min = arr[0], max = arr[0];
        for (int num : arr) {
            if (num < min) min = num;
            if (num > max) max = num;
        }
        // 从 min 到 max 的计数数组
        int n = max - min + 1;
        int[] count = new int[n];
        // 计数
        for (int num : arr) {
            count[num - min]++;
        }
        // 累加，计算其所在的位置
        for (int i = 1; i < n; i++) {
            count[i] += count[i - 1];
        }
        int[] out = new int[arr.length];
        //  从后向前遍历是为了保持相同元素的稳定性
        //  count-- 是为了保证从后向前第二个相同元素的索引正确
        for (int i = arr.length - 1; i >= 0; i--) {
            out[count[arr[i] - min] - 1] = arr[i];
            count[arr[i] - min]--;
        }
        System.arraycopy(out, 0, arr, 0, arr.length);
    }

    // 堆排序的基本思想是将待排序的数据 调整成一个最大堆或最小堆，
    // 然后通过不断取出堆顶元素来实现排序，同时让这个堆保持最大堆的性质
    // 需要自己手写堆排序算法
    public static void heapSort(int[] arr) {
        int n = arr.length;
        // 构造一个大顶堆
        for (int i = n / 2; i >= 0; i--) {
            maxHeapify(arr, n, i);
        }
        // 不断的把堆顶元素放到数组最后
        for (int i = n - 1; i > 0; i--) {
            swap(arr, 0, i);
            // 调整堆
            maxHeapify(arr, i, 0);
        }
    }

    /**
     * 完美二叉树的 左右子树的节点 索引 2*i+1，2*i+2
     * 新增节点是下溢，删除节点是先和堆顶元素交换，再下溢
     *
     * @param arr 要操作的数组
     * @param n   当前堆的有效大小
     * @param i   需要进行堆化的 节点索引
     */
    private static void maxHeapify(int[] arr, int n, int i) {
        // 操作的一定是索引
        int max = i;
        int l = 2 * i + 1;
        int r = 2 * i + 2;
        if (l < n && arr[l] > arr[max]) max = l;
        if (r < n && arr[r] > arr[max]) max = r;
        if (max != i) {
            swap(arr, i, max);
            maxHeapify(arr, n, max);
        }
    }

    private static void minHeapify(int[] arr, int n, int i) {
        // 操作的一定是索引
        int min = i;
        int l = 2 * i + 1;
        int r = 2 * i + 2;
        if (l < n && arr[l] < arr[min]) min = l;
        if (r < n && arr[r] < arr[min]) min = r;
        if (min != i) {
            swap(arr, i, min);
            minHeapify(arr, n, min);
        }
    }


    // 215：100题 数组中的第 k 个最大元素
    public int findKthLargest(int[] nums, int k) {

        int n = nums.length;
        for (int i = n / 2 - 1; i > 0; i--) {
            minHeapify(nums, n, i);
        }
        for (int i = n - 1; i > 0; i--) {
            swap(nums, 0, i);
            minHeapify(nums, i, 0);
        }
        return nums[k - 1];
    }

    // 347：100题 前 k 个高频元素：https://leetcode.cn/problems/top-k-frequent-elements/
    // 使用原地排序算法
    public int[] topKFrequent(int[] nums, int k) {
        // 用于存储每个元素的频率
        Map<Integer, Integer> map = new HashMap<>();
        // 统计每个数字的频率
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        PriorityQueue<Map.Entry<Integer, Integer>> queue = new PriorityQueue<>(
                (a, b) -> b.getValue() - a.getValue()
        );
        queue.addAll(map.entrySet());

        // 取出 k 个频率最高的元素
        int[] ans = new int[k];
        for (int i = 0; i < k; i++) {
            ans[i] = queue.poll().getKey();
        }
        return ans;
    }

    // 692：前 k 个高频单词

    // 912：排序数组

    // 295：hard（暂时不做）数据流的中位数：https://leetcode.cn/problems/find-median-from-data-stream/

    //===========================================================
    // 两个数组的交集
    public int[] intersect1(int[] nums1, int[] nums2) {
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        int l1 = nums1.length, l2 = nums2.length;

        // 最简单的三指针问题
        int p = 0, q = 0, i = 0;

        int[] ans = new int[Math.min(l1, l2)];
        while (p < l1 && q < l2) {
            if (nums1[p] < nums2[q])
                p++;
            else if (nums1[p] > nums2[q])
                q++;
            else if (nums1[p] == nums2[q]) {
                ans[i++] = nums1[p];
                p++;
                q++;
            }
        }
        return Arrays.copyOfRange(ans, 0, i);
    }

    // 哈希表存的是 num1 的元素，以及它出现的次数
    // 然后拿着 num2 的元素去 map 里面找，操作
    public int[] intersect(int[] nums1, int[] nums2) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int num : nums1) {
            map.put(num, map.getOrDefault(num, 0));
        }
        int n = Math.min(nums1.length, nums2.length);
        int[] ans = new int[n];
        int i = 0;
        for (int num : nums2) {
            Integer count = map.getOrDefault(num, 0);
            if (count > 0) {
                ans[i++] = num;
                count--;
                if (count > 0) {
                    map.put(num, count);
                } else {
                    map.remove(num);
                }
            }
        }
        return Arrays.copyOfRange(ans, 0, i);
    }
}
















